import Log from './log'
import DelayPromise from './delay.promise'

const kModeWrite = 'readwrite'
const kModeRead = 'readonly'

let dp = new DelayPromise()
let log = new Log('db>')

let db = {
  open: (db, table = null, version = 0) => {
    const key = 'db-' + db
    const check = dp.prepare(key, true)

    if (check) {
      return dp.keep(key)
    }

    let r = (0 === version) ?
        indexedDB.open(db) : indexedDB.open(db, version)

    r.onerror = e => dp.done(key, e.target.error)
    r.onsuccess = e => dp.done(key, null, e.target.result)

    r.onupgradeneeded = e => {
      let db = e.target.result
      for (let i in table) {
        const name = table[i].name
        const remove = table[i].remove || false
        const refresh = table[i].refresh || false
        const key = table[i].key || null
        const indexes = table[i].indexes || []

        if (remove || refresh) {
          if (db.objectStoreNames.contains(name)) {
            log.w('delete store:', name)
            db.deleteObjectStore(name)
          }
        }

        if (!remove && !db.objectStoreNames.contains(name)) {
          let store = db.createObjectStore(name,
              null != key ? {keyPath: key} : {autoIncrement: true})

          indexes.forEach(index => {
            const name = index.name || null
            const unique = index.unique || false

            if (null === index) {
              return
            }

            if (!store.indexNames.contains(name)) {
              store.createIndex(name, name, {unique: unique})
            }
          })

        }
      }
    }

    return dp.promise(key)
  },

  truncate: (db, table) => {
    const key = dp.genKey('db-truncate-' + db.name + '-' + table)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeWrite)
    transaction.onerror = e => dp.done(key, e.target.error)

    let r = transaction.objectStore(table).clear()
    r.onsuccess = _ => dp.done(key, null, db)

    return dp.promise(key)
  },

  add: (db, table, data) => {
    const key = dp.genKey('db-add-' + db.name + '-' + table)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeWrite)
    transaction.onerror = e => dp.done(key, e.target.error)
    transaction.oncomplete = _ => dp.done(key, null, db)

    let store = transaction.objectStore(table)

    if (Array.isArray(data)) {
      data.forEach(d => store.add(d))
    } else {
      store.add(data)
    }

    return dp.promise(key)
  },

  delete: (db, table, k) => {
    const key = dp.genKey('db-delete-' + table + '-' + k)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeWrite)
    transaction.onerror = e => dp.done(key, e.target.error)

    let r = transaction.objectStore(table).delete(k)
    r.onsuccess = _ => dp.done(key, null, db)

    return dp.promise(key)
  },

  deleteByIndex: (db, table, index, cb) => {
    const key = dp.genKey('db-delete-' + table + '-' + index)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeWrite)
    transaction.onerror = e => dp.done(key, e.target.error)

    transaction.objectStore(table).index(index).openCursor()
        .onsuccess = e => {
      let cursor = e.target.result

      if (cursor) {
        if (cb(cursor.value)) {
          cursor.delete();
        }

        cursor.continue()
      } else {
        dp.done(key)
      }
    }

    return dp.promise(key)
  },

  deleteWhenIndex: (db, table, index, value) => {
    const key = dp.genKey('db-delete-' + table + '-' + index + '=' + value)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeWrite)
    transaction.onerror = e => dp.done(key, e.target.error)

    transaction.objectStore(table).index(index).openCursor()
        .onsuccess = e => {
      let cursor = e.target.result

      if (cursor) {
        if (value === cursor.value[index]) {
          cursor.delete();
        }

        cursor.continue()
      } else {
        dp.done(key)
      }
    }

    return dp.promise(key)
  },

  get: (db, table, k) => {
    const key = dp.genKey('db-get-' + table + '-' + k)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeRead)
    transaction.onerror = e => dp.done(key, e.target.error)

    let r = transaction.objectStore(table).get(k)
    r.onsuccess = _ => dp.done(key, null, {data: db, result: r.result})

    return dp.promise(key)
  },

  getByIndex: (db, table, index, k) => {
    const key = dp.genKey('db-get-' + table + '-' + index + '-' + k)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeRead)
    transaction.onerror = e => dp.done(key, e.target.error)

    transaction.objectStore(table).index(index).get(k).onsuccess =
        e => dp.done(key, null, {data: db, result: e.target.result})

    return dp.promise(key)
  },

  set: (db, table, k, v) => {
    const key = dp.genKey('db-set-' + table + '-' + k)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeWrite)
    transaction.onerror = e => dp.done(key, e.target.error)

    let store = transaction.objectStore(table)
    store.get(k).onsuccess = _ => {
      store.put(v).onsuccess = _ => dp.done(key, null, db)
    }

    return dp.promise(key)
  },

  setBatch: (db, table, keys, data) => {
    const key = dp.genKey('db-set-' + table + '-' + keys + '.' + keys.length)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeWrite)
    transaction.onerror = e => dp.done(key, e.target.error)
    transaction.oncomplete = _ => dp.done(key, null, db)

    let store = transaction.objectStore(table)
    if (Array.isArray(keys)) {
      for (let i in keys) {
        store.get(keys[i]).onsuccess = _ => store.put(data[i])
      }
    } else {
      store.get(keys).onsuccess = _ => store.put(data)
    }

    return dp.promise(key)
  },

  update: (db, table, k, cb) => {
    const key = dp.genKey('db-update-' + table + '-' + k)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeWrite)
    transaction.onerror = e => dp.done(key, e.target.error)

    let store = transaction.objectStore(table)
    store.get(k).onsuccess = e => {
      const value = cb(e.target.result)
      if (null != value) {
        store.put(value).onsuccess = _ => dp.done(key, null, db)
      } else {
        dp.done(key, 'update key: ' + k + ' with null cb')
      }
    }

    return dp.promise(key)
  },

  updateBatch: (db, table, keys, data, cb) => {
    const key = dp.genKey('db-update-' + table + '-' + keys + '.' + keys.length)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeWrite)
    transaction.onerror = e => dp.done(key, e.target.error)
    transaction.oncomplete = _ => dp.done(key, null, db)

    let store = transaction.objectStore(table)

    if (Array.isArray(data)) {
      for (let i in keys) {
        store.get(keys[i]).onsuccess = e => {
          const value = cb(e.target.result, data[i])
          // [Neo] .TODO batch failed catch
          if (null != value) {
            store.put(value)
          } else {
            dp.done(key, 'updateBatch k: ' + keys + ' with null cb')
          }
        }
      }
    } else {
      store.get(keys).onsuccess = e => {
        const value = cb(e.target.result, data)
        if (null != value) {
          store.put(value)
        } else {
          dp.done(key, 'updateBatch k: ' + keys + ' with null cb')
        }
      }
    }

    return dp.promise(key)
  },

  list: (db, table, filter = null,
      lower = null, upper = null, asc = true, unique = false, limit = -1) => {
    const key = dp.genKey('db-list-' + table)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeRead)
    transaction.onerror = e => dp.done(key, e.target.error)

    let range = null
    if (null != lower && null != upper) {
      range = IDBKeyRange.bound(lower, upper)
    } else if (null != lower) {
      range = IDBKeyRange.lowerBound(lower)
    } else if (null != upper) {
      range = IDBKeyRange.upperBound(upper)
    }

    let direction = 'next'
    if (asc && unique) {
      direction = 'nextunique'
    } else if (!asc && !unique) {
      direction = 'prev'
    } else if (!asc && unique) {
      direction = 'prevunique'
    }

    transaction.objectStore(table).openCursor(range, direction)
        .onsuccess = e => {
      let cursor = e.target.result

      if (cursor) {

        if (0 !== limit) {
          let v = cursor.value
          if (null == filter) {
            limit -= 1
            dp.append(key, v)
          } else {
            if (filter(v)) {
              limit -= 1
              dp.append(key, v)
            }
          }

          cursor.continue()
        } else {
          cursor.advance(1)
        }

      } else {
        dp.appendDone(key, db)
      }
    }

    return dp.promise(key)
  },

  last: function (db, table, filter = null, limit = 1) {
    return this.list(db, table, filter, null, null, false, false, limit)
  },

  listByIndex: (db, table, index, filter = null,
      lower = null, upper = null, asc = true, unique = false, limit = -1) => {
    const key = dp.genKey('db-list-' + table + '-' + index)
    dp.prepare(key)

    db = db.hasOwnProperty('data') ? db.data : db
    let transaction = db.transaction(table, kModeRead)
    transaction.onerror = e => dp.done(key, e.target.error)

    let range = null
    if (null != lower && null != upper) {
      range = IDBKeyRange.bound(lower, upper)
    } else if (null != lower) {
      range = IDBKeyRange.lowerBound(lower)
    } else if (null != upper) {
      range = IDBKeyRange.upperBound(upper)
    }

    let direction = 'next'
    if (asc && unique) {
      direction = 'nextunique'
    } else if (!asc && !unique) {
      direction = 'prev'
    } else if (!asc && unique) {
      direction = 'prevunique'
    }

    transaction.objectStore(table).index(index).openCursor(range, direction)
        .onsuccess = e => {
      let cursor = e.target.result

      if (cursor) {

        if (0 !== limit) {
          let v = cursor.value
          if (null == filter) {
            limit -= 1
            dp.append(key, v)
          } else {
            if (filter(v)) {
              limit -= 1
              dp.append(key, v)
            }
          }

          cursor.continue()
        } else {
          cursor.advance(1)
        }

      } else {
        dp.appendDone(key, db)
      }

    }

    return dp.promise(key)
  },

  lastByIndex: function (db, table, index, filter = null, limit = 1) {
    return this.listByIndex(db, table, index, filter,
        null, null, false, false, limit)
  },

  close: db => {
    db = db.hasOwnProperty('data') ? db.data : db
    db.close()

    dp.clear('db-' + db.name)
    return dp.good('db.close')
  },
}

export default db;
